2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "adiumPurpleEventloop.h"
18 #import <AIUtilities/AIApplicationAdditions.h>
21 #include <sys/socket.h>
22 #include <sys/select.h>
24 //#define PURPLE_SOCKET_DEBUG
26 static guint sourceId = 0; //The next source key; continuously incrementing
27 static CFRunLoopRef purpleRunLoop = nil;
29 static void socketCallback(CFSocketRef s,
30 CFSocketCallBackType callbackType,
35 * The sources, keyed by integer key id (wrapped in an NSNumber), holding
36 * SourceInfo * objects
38 static NSMutableDictionary *sourceInfoDict = nil;
41 * @class Holder for various source/timer information
43 * This serves as the context info for source and timer callbacks. We use it just as a
44 * struct (declaring all the class's ivars to be public) but make it an object so we can use
45 * reference counting on it easily.
47 @interface SourceInfo : NSObject {
49 @public CFSocketRef socket;
51 @public CFRunLoopSourceRef run_loop_source;
53 @public guint timer_tag;
54 @public GSourceFunc timer_function;
55 @public CFRunLoopTimerRef timer;
56 @public gpointer timer_user_data;
58 @public guint read_tag;
59 @public PurpleInputFunction read_ioFunction;
60 @public gpointer read_user_data;
62 @public guint write_tag;
63 @public PurpleInputFunction write_ioFunction;
64 @public gpointer write_user_data;
68 @implementation SourceInfo
69 - (NSString *)description
71 return [NSString stringWithFormat:@"<SourceInfo %p: Socket %p: fd %i; timer_tag %i; read_tag %i; write_tag %i>",
72 self, socket, fd, timer_tag, read_tag, write_tag];
76 static SourceInfo *createSourceInfo(void)
78 SourceInfo *info = [[SourceInfo alloc] init];
82 info->run_loop_source = NULL;
85 info->timer_function = NULL;
87 info->timer_user_data = NULL;
90 info->write_ioFunction = NULL;
91 info->write_user_data = NULL;
94 info->read_ioFunction = NULL;
95 info->read_user_data = NULL;
103 * @brief Given a SourceInfo struct for a socket which was for reading *and* writing, recreate its socket to be for just one
105 * If the sourceInfo still has a read_tag, the resulting CFSocket will be just for reading.
106 * If the sourceInfo still has a write_tag, the resulting CFSocket will be just for writing.
108 * This is necessary to prevent the now-unneeded condition from triggerring its callback.
110 void updateSocketForSourceInfo(SourceInfo *sourceInfo)
112 CFSocketRef socket = sourceInfo->socket;
117 if (sourceInfo->read_tag)
118 CFSocketEnableCallBacks(socket, kCFSocketReadCallBack);
120 CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);
123 if (sourceInfo->write_tag)
124 CFSocketEnableCallBacks(socket, kCFSocketWriteCallBack);
126 CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
128 //Re-enable callbacks automatically and, by starting with 0, _don't_ close the socket on invalidate
129 CFOptionFlags flags = 0;
131 if (sourceInfo->read_tag) flags |= kCFSocketAutomaticallyReenableReadCallBack;
132 if (sourceInfo->write_tag) flags |= kCFSocketAutomaticallyReenableWriteCallBack;
134 CFSocketSetSocketFlags(socket, flags);
138 gboolean adium_source_remove(guint tag) {
139 SourceInfo *sourceInfo = (SourceInfo *)[sourceInfoDict objectForKey:[NSNumber numberWithUnsignedInt:tag]];
142 #ifdef PURPLE_SOCKET_DEBUG
143 AILog(@"adium_source_remove(): Removing for fd %i [sourceInfo %x]: tag is %i (timer %i, read %i, write %i)",sourceInfo->fd,
144 sourceInfo, tag, sourceInfo->timer_tag, sourceInfo->read_tag, sourceInfo->write_tag);
146 if (sourceInfo->timer_tag == tag) {
147 sourceInfo->timer_tag = 0;
149 } else if (sourceInfo->read_tag == tag) {
150 sourceInfo->read_tag = 0;
152 } else if (sourceInfo->write_tag == tag) {
153 sourceInfo->write_tag = 0;
157 if (sourceInfo->timer_tag == 0 && sourceInfo->read_tag == 0 && sourceInfo->write_tag == 0) {
159 if (sourceInfo->timer) {
160 CFRunLoopTimerInvalidate(sourceInfo->timer);
161 CFRelease(sourceInfo->timer);
162 sourceInfo->timer = NULL;
165 if (sourceInfo->socket) {
166 #ifdef PURPLE_SOCKET_DEBUG
167 AILog(@"adium_source_remove(): Done with a socket %x, so invalidating it",sourceInfo->socket);
169 CFSocketInvalidate(sourceInfo->socket);
170 CFRelease(sourceInfo->socket);
171 sourceInfo->socket = NULL;
174 if (sourceInfo->run_loop_source) {
175 CFRelease(sourceInfo->run_loop_source);
176 sourceInfo->run_loop_source = NULL;
179 if ((sourceInfo->timer_tag == 0) && (sourceInfo->timer)) {
180 CFRunLoopTimerInvalidate(sourceInfo->timer);
181 CFRelease(sourceInfo->timer);
182 sourceInfo->timer = NULL;
185 if (sourceInfo->socket && (sourceInfo->read_tag || sourceInfo->write_tag)) {
186 #ifdef PURPLE_SOCKET_DEBUG
187 AILog(@"adium_source_remove(): Calling updateSocketForSourceInfo(%x)",sourceInfo);
189 updateSocketForSourceInfo(sourceInfo);
193 [sourceInfoDict removeObjectForKey:[NSNumber numberWithUnsignedInt:tag]];
201 //Like g_source_remove, return TRUE if successful, FALSE if not
202 gboolean adium_timeout_remove(guint tag) {
203 return (adium_source_remove(tag));
208 void callTimerFunc(CFRunLoopTimerRef timer, void *info)
210 SourceInfo *sourceInfo = info;
212 if (![sourceInfoDict objectForKey:[NSNumber numberWithUnsignedInt:sourceInfo->timer_tag]])
213 NSLog(@"**** WARNING: %@ has already been removed, but we're calling its timer function!", info);
215 if (!sourceInfo->timer_function ||
216 !sourceInfo->timer_function(sourceInfo->timer_user_data)) {
217 adium_source_remove(sourceInfo->timer_tag);
221 guint adium_timeout_add(guint interval, GSourceFunc function, gpointer data)
223 SourceInfo *info = createSourceInfo();
225 NSTimeInterval intervalInSec = (NSTimeInterval)interval/1000;
227 CFRunLoopTimerContext runLoopTimerContext = { 0, info, CFRetain, CFRelease, /* CFAllocatorCopyDescriptionCallBack */ NULL };
228 CFRunLoopTimerRef runLoopTimer = CFRunLoopTimerCreate(
229 NULL, /* default allocator */
230 (CFAbsoluteTimeGetCurrent() + intervalInSec), /* The time at which the timer should first fire */
231 intervalInSec, /* firing interval */
232 0, /* flags, currently ignored */
233 0, /* order, currently ignored */
234 callTimerFunc, /* CFRunLoopTimerCallBack callout */
235 &runLoopTimerContext /* context */
237 CFRunLoopAddTimer(purpleRunLoop, runLoopTimer, kCFRunLoopCommonModes);
240 info->timer_function = function;
241 info->timer = runLoopTimer;
242 info->timer_user_data = data;
243 info->timer_tag = ++sourceId;
245 [sourceInfoDict setObject:info
246 forKey:[NSNumber numberWithUnsignedInt:info->timer_tag]];
248 return info->timer_tag;
251 guint adium_input_add(int fd, PurpleInputCondition condition,
252 PurpleInputFunction func, gpointer user_data)
255 NSLog(@"INVALID: fd was %i; returning tag %i",fd,sourceId+1);
259 SourceInfo *info = createSourceInfo();
261 // And likewise the entire CFSocket
262 CFSocketContext context = { 0, info, CFRetain, CFRelease, /* CFAllocatorCopyDescriptionCallBack */ NULL };
265 * From CFSocketCreateWithNative:
266 * If a socket already exists on this fd, CFSocketCreateWithNative() will return that existing socket, and the other parameters
269 #ifdef PURPLE_SOCKET_DEBUG
270 AILog(@"adium_input_add(): Adding input %i on fd %i", condition, fd);
272 CFSocketRef socket = CFSocketCreateWithNative(NULL,
274 (kCFSocketReadCallBack | kCFSocketWriteCallBack),
278 /* If we did not create a *new* socket, it is because there is already one for this fd in the run loop.
279 * See the CFSocketCreateWithNative() documentation), add it to the run loop.
280 * In that case, the socket's info was not updated.
282 CFSocketContext actualSocketContext = { 0, NULL, NULL, NULL, NULL };
283 CFSocketGetContext(socket, &actualSocketContext);
284 if (actualSocketContext.info != info) {
287 info = [(SourceInfo *)(actualSocketContext.info) retain];
291 info->socket = socket;
293 if ((condition & PURPLE_INPUT_READ)) {
294 info->read_tag = ++sourceId;
295 info->read_ioFunction = func;
296 info->read_user_data = user_data;
298 [sourceInfoDict setObject:info
299 forKey:[NSNumber numberWithUnsignedInt:info->read_tag]];
302 info->write_tag = ++sourceId;
303 info->write_ioFunction = func;
304 info->write_user_data = user_data;
306 [sourceInfoDict setObject:info
307 forKey:[NSNumber numberWithUnsignedInt:info->write_tag]];
310 updateSocketForSourceInfo(info);
312 //Add it to our run loop
313 if (!(info->run_loop_source)) {
314 info->run_loop_source = CFSocketCreateRunLoopSource(NULL, socket, 0);
315 if (info->run_loop_source) {
316 CFRunLoopAddSource(purpleRunLoop, info->run_loop_source, kCFRunLoopCommonModes);
318 AILog(@"*** Unable to create run loop source for %p",socket);
327 #pragma mark Socket Callback
328 static void socketCallback(CFSocketRef s,
329 CFSocketCallBackType callbackType,
334 SourceInfo *sourceInfo = (SourceInfo *)infoVoid;
336 PurpleInputCondition c;
337 PurpleInputFunction ioFunction = NULL;
338 gint fd = sourceInfo->fd;
340 if ((callbackType & kCFSocketReadCallBack)) {
341 if (sourceInfo->read_tag) {
342 user_data = sourceInfo->read_user_data;
343 c = PURPLE_INPUT_READ;
344 ioFunction = sourceInfo->read_ioFunction;
346 AILog(@"Called read with no read_tag (read_tag %i write_tag %i) for %x",
347 sourceInfo->read_tag, sourceInfo->write_tag, sourceInfo->socket);
350 } else /* if ((callbackType & kCFSocketWriteCallBack)) */ {
351 if (sourceInfo->write_tag) {
352 user_data = sourceInfo->write_user_data;
353 c = PURPLE_INPUT_WRITE;
354 ioFunction = sourceInfo->write_ioFunction;
356 AILog(@"Called write with no write_tag (read_tag %i write_tag %i) for %x",
357 sourceInfo->read_tag, sourceInfo->write_tag, sourceInfo->socket);
362 #ifdef PURPLE_SOCKET_DEBUG
363 AILog(@"socketCallback(): Calling the ioFunction for %x, callback type %i (%s: tag is %i)",s,callbackType,
364 ((callbackType & kCFSocketReadCallBack) ? "reading" : "writing"),
365 ((callbackType & kCFSocketReadCallBack) ? sourceInfo->read_tag : sourceInfo->write_tag));
367 ioFunction(user_data, fd, c);
371 int adium_input_get_error(int fd, int *error)
375 len = sizeof(*error);
377 ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, error, &len);
378 if (!ret && !(*error)) {
380 * Taken from Fire's FaimP2PConnection.m:
381 * The job of this function is to detect if the connection failed or not
382 * There has to be a better way to do this
384 * Any socket that fails to connect will select for reading and writing
385 * and all reads and writes will fail
386 * Any listening socket will select for reading, and any read will fail
387 * So, select for writing, if you can write, and the write fails, not connected
392 struct timeval timeout;
398 select(fd+1, NULL, &thisfd, NULL, &timeout);
399 if(FD_ISSET(fd, &thisfd)){
401 char buffer[4] = {0, 0, 0, 0};
403 length = write(fd, buffer, length);
409 AILog(@"adium_input_get_error(%i): Socket is NOT valid", fd);
418 static PurpleEventLoopUiOps adiumEventLoopUiOps = {
420 adium_timeout_remove,
423 adium_input_get_error,
424 /* timeout_add_seconds */ NULL
427 PurpleEventLoopUiOps *adium_purple_eventloop_get_ui_ops(void)
429 if (!sourceInfoDict) sourceInfoDict = [[NSMutableDictionary alloc] init];
431 //Determine our run loop
432 purpleRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
433 CFRetain(purpleRunLoop);
435 return &adiumEventLoopUiOps;